//
// This file is part of cpp-lib, a library of C++ functions and classes.
// Cpp-lib is copyright (C) 2004 and onwards, The CPL Interest Group.
// 
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 2.1 of the License, or (at
// your option) any later version.
// 
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
// General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with this library (see the file COPYING); if not, write to the
// Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
// 02111-1307  USA
//
// $Id$
//

#include <iostream>
#include <iterator>
#include <string>
#include <sstream>
#include <exception>
#include <stdexcept>
#include <algorithm>
#include <memory>

#include <cassert>

#include "cpp-lib/network.h"
#include "cpp-lib/util.h"

using namespace cpl::util::network ;


void usage( std::string const& name ) {

  std::cerr << 
"usage: " << name << " cat | reverse | connect [ host ] [ port ]\n"
"cat      port:       Wait for connection and copy TCP stream to stdout.\n"
"reverse  port:       Start a reverse server.\n"
"hello    port:       Start a hello world server.\n"
"connect  host port:  Connect, copy stdin into connection and then\n"
"                     connection to stdout.\n"
"wget     URL:        Request URL using HTTP/1.0 and dump the content\n"
"                     (including HTTP headers!) to stdout.\n"
  ;

}


struct reverse_server {

  reverse_server( std::auto_ptr< connection > c )
  : c( c ) {}

  void operator()() {
    
    nstream ns( *c ) ;

    ns << "500 Welcome to the REVERSE server.\n"
          "501 Please type the strings you would like to have reversed.\n"
          "501 Type ``quit'' to end the session."
       << std::endl ;

    std::string s ;
    while( ns >> s ) { 
      
      if( s == "quit" ) { ns << "550 Goodbye!" << std::endl ; break ; }

      reverse( s.begin() , s.end() ) ; 
      ns << s << std::endl ; 
    
    }

  }

private:

  std::auto_ptr< connection > c ;

} ;


void run_hello_server( std::string const& port ) {
  
  acceptor a( port ) ;
  std::cerr << "Hello world server version 0.01 listening on " 
            << a.local()
	    << std::endl ;

  while( 1 ) {

    connection c( a ) ;
    std::cerr << "Connection from: " << c.peer() << std::endl ;

    onstream os( c ) ;
    
    os << "Hello world!  How nice to meet you.\n" ;
    
    std::cerr << "Closing connection." << std::endl ;

  }

}


void run_reverse_server( std::string const& port ) {
  
  acceptor a( port ) ;
  std::cerr << "Reverse server version 0.01 listening on " 
            << a.local()
	    << std::endl ;

  while( 1 ) {

    std::auto_ptr< connection > c( new connection( a ) ) ;

    // Gave up on threading for the moment...
    // new boost::thread( reverse_server( c ) ) ;

    std::cerr << "Connection from: " << c->peer() << std::endl ;
    reverse_server rs( c ) ;
    rs() ;
    // Join thread...
    std::cerr << "Connection closed." << std::endl ;

  }

}


void stream_copy( std::istream& is , std::ostream& os ) {
  
  std::copy( 
    std::istreambuf_iterator< char >( is ) ,
    std::istreambuf_iterator< char >(    ) ,
    std::ostreambuf_iterator< char >( os )
  ) ;

}
    

void receive_data( connection& c , std::ostream& os = std::cout ) {

  std::cerr << "Data received:" << std::endl ;
  
  instream is( c ) ;
  stream_copy( is , os ) ;

  std::cerr << "EOF from server." << std::endl ;

}


void print_connection( connection const& c ) {

  std::cerr << "Local address: " << c.local() << std::endl ;
  std::cerr << "Peer address: "  << c.peer () << std::endl ;

}


std::string const newline = "\r\n" ;
std::string const whitespace = "\t\n\r " ; 

bool blank( std::string const& s ) {

  return std::string::npos == s.find_first_not_of( whitespace ) ;

}


void wget( 
  std::ostream& os        ,
  std::string const& path , 
  std::string const& host ,
  std::string const& port = "80" ,
  std::string const& from = "ano@nymous.com" ,
  std::string const& user_agent = "tcp-test/0.01"
) {
  
  connection c( host , port ) ;
  print_connection( c ) ;

  // See [3] (reference in README) for a quick HTTP intro. 

  std::ostringstream oss ;
  oss << "GET "         << path << " HTTP/1.0"  << newline
      << "From: "       << from                 << newline
      << "Host: "       << host << ":" << port  << newline
      << "User-Agent: " << user_agent           << newline
  // This is HTTP/1.1
  //  << "Connection: close"                    << newline
                                                << newline
  ;
    
  std::cerr << "Sending request:\n"
            << oss.str()
            << std::endl 
  ;
  
  onstream ons( c ) ;
  ons << oss.str() << std::flush ;
  
  std::cerr << "Server reply HTTP header (stderr):" << std::endl ;

  instream ins( c ) ;
  std::string line ;

  while( std::getline( ins , line ) ) {

    if( blank( line ) ) { break ; }
    std::cerr << line << std::endl ;
  
  }

  std::cerr << "Server reply data:" << std::endl ;
  
  stream_copy( ins , os ) ;

  std::cerr << "EOF from server." << std::endl ;

}


//
// Very primitive parsing URL into hostname, port and path, submit an
// HTTP request and pipe the result to os.
//

void wget( std::ostream& os , std::string url ) {

  if( "http://" != url.substr( 0 , 7 ) )
  { throw std::runtime_error( "URL must start with http://" ) ; }

  url = url.substr( 7 ) ;

  std::string::size_type const slash = url.find_first_of( '/' ) ;

  if( 0 == slash ) 
  { throw std::runtime_error( "bad URL format: No host[:port] parsed" ) ; }

  if( std::string::npos == slash )
  { throw std::runtime_error( "bad URL format: No slash after host[:port]" ) ; }
  
  std::string const path     = url.substr( slash     ) ;
  std::string const hostport = url.substr( 0 , slash ) ;

  std::string::size_type const colon = hostport.find_first_of( ':' ) ;

  if( hostport.size() - 1 == colon ) { 

    throw std::runtime_error
    ( "bad URL format: colon after hostname, but no port" ) ; 
  
  }
  
  if( 0 == colon ) { 

    throw std::runtime_error
    ( "bad URL format: no hostname before colon" ) ; 
  
  }

  std::string const port = std::string::npos == colon ? 
    "80" : hostport.substr( colon + 1 ) ;

  std::string const host = hostport.substr( 0 , colon ) ;

  assert( host.size() ) ;
  assert( port.size() ) ;

  wget( os , path , host , port ) ;

}


int main( int argc , char const* const* const argv ) {

  try {

  if( argc <= 2 || 5 <= argc ) {

    usage( argv[ 0 ] ) ; 
    return 1 ;

  }
  
  std::string const command = argv[ 1 ] ;

  if( std::string( "cat" ) == command ) {

    if( 3 != argc ) { usage( argv[ 0 ] ) ; return 1 ; }
  
    acceptor a( argv[ 2 ] ) ;
    std::cerr << "Listening on: " << a.local() << std::endl ;

    connection c( a ) ;
    std::cerr << "Connection from: " << c.peer() << std::endl ;

    receive_data( c , std::cout ) ;

  } else if( "reverse" == command ) {
  
    if( 3 != argc ) { usage( argv[ 0 ] ) ; return 1 ; }
    run_reverse_server( argv[ 2 ] ) ;

  } else if( "hello" == command ) {
    
    if( 3 != argc ) { usage( argv[ 0 ] ) ; return 1 ; }
    run_hello_server( argv[ 2 ] ) ;
    
  } else if( "connect" == command ) {

    if( 4 != argc ) { usage( argv[ 0 ] ) ; return 1 ; }

    connection c( argv[ 2 ] , argv[ 3 ] ) ;
    print_connection( c ) ;

    std::cerr << "Enter request, terminate by Ctrl-D." << std::endl ;

    onstream os( c ) ;
    stream_copy( std::cin , os ) ;
    os << std::flush ;

    std::cerr << "Request sent, waiting for reply." << std::endl ;
    
    receive_data( c , std::cout ) ;

  } else if( "wget" == command ) { 

    if( 3 != argc ) { usage( argv[ 0 ] ) ; return 1 ; }
    wget( std::cout , argv[ 2 ] ) ;

  } else {
    
    usage( argv[ 0 ] ) ; 
    return 1 ;

  }

  } // end global try
  catch( std::exception const& e ) 
  { cpl::util::die( e.what() ) ; }

}
